/*
 * Copyright 2012-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.web;

import java.io.File;
import java.net.InetAddress;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import io.undertow.UndertowOptions;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import org.springframework.boot.convert.DurationUnit;
import org.springframework.boot.web.server.Compression;
import org.springframework.boot.web.server.Cookie;
import org.springframework.boot.web.server.Http2;
import org.springframework.boot.web.server.Shutdown;
import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.servlet.server.Encoding;
import org.springframework.boot.web.servlet.server.Jsp;
import org.springframework.boot.web.servlet.server.Session;
import org.springframework.util.StringUtils;
import org.springframework.util.unit.DataSize;

/**
 * Web 服务器的 {@link ConfigurationProperties 配置属性}（例如端口和路径设置）。
 *
 * @author Dave Syer
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @author Ivan Sopov
 * @author Marcos Barbero
 * @author Eddú Meléndez
 * @author Quinten De Swaef
 * @author Venil Noronha
 * @author Aurélien Leboulanger
 * @author Brian Clozel
 * @author Olivier Lamy
 * @author Chentao Qu
 * @author Artsiom Yudovin
 * @author Andrew McGhie
 * @author Rafiullah Hamedy
 * @author Dirk Deyne
 * @author HaiTao Zhang
 * @author Victor Mandujano
 * @author Chris Bono
 * @author Parviz Rozikov
 * @since 1.0.0
 */
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {

	/**
	 * 服务器 HTTP 端口。
	 */
	private Integer port;

	/**
	 * 服务器应绑定的网络地址。
	 */
	private InetAddress address;

	@NestedConfigurationProperty
	private final ErrorProperties error = new ErrorProperties();

	/**
	 * 处理 X-Forwarded-* 请求头的策略。
	 */
	private ForwardHeadersStrategy forwardHeadersStrategy;

	/**
	 * 用于 Server 响应头的值（如果为空，则不发送该头）。
	 */
	private String serverHeader;

	/**
	 * HTTP 消息头的最大大小。
	 */
	private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8);

	/**
	 * 服务器支持的关闭类型。
	 */
	private Shutdown shutdown = Shutdown.IMMEDIATE;

	@NestedConfigurationProperty
	private Ssl ssl;

	@NestedConfigurationProperty
	private final Compression compression = new Compression();

	@NestedConfigurationProperty
	private final Http2 http2 = new Http2();

	private final Servlet servlet = new Servlet();

	private final Reactive reactive = new Reactive();

	private final Tomcat tomcat = new Tomcat();

	private final Jetty jetty = new Jetty();

	private final Netty netty = new Netty();

	private final Undertow undertow = new Undertow();

	public Integer getPort() {
		return this.port;
	}

	public void setPort(Integer port) {
		this.port = port;
	}

	public InetAddress getAddress() {
		return this.address;
	}

	public void setAddress(InetAddress address) {
		this.address = address;
	}

	public String getServerHeader() {
		return this.serverHeader;
	}

	public void setServerHeader(String serverHeader) {
		this.serverHeader = serverHeader;
	}

	public DataSize getMaxHttpHeaderSize() {
		return this.maxHttpHeaderSize;
	}

	public void setMaxHttpHeaderSize(DataSize maxHttpHeaderSize) {
		this.maxHttpHeaderSize = maxHttpHeaderSize;
	}

	public Shutdown getShutdown() {
		return this.shutdown;
	}

	public void setShutdown(Shutdown shutdown) {
		this.shutdown = shutdown;
	}

	public ErrorProperties getError() {
		return this.error;
	}

	public Ssl getSsl() {
		return this.ssl;
	}

	public void setSsl(Ssl ssl) {
		this.ssl = ssl;
	}

	public Compression getCompression() {
		return this.compression;
	}

	public Http2 getHttp2() {
		return this.http2;
	}

	public Servlet getServlet() {
		return this.servlet;
	}

	public Reactive getReactive() {
		return this.reactive;
	}

	public Tomcat getTomcat() {
		return this.tomcat;
	}

	public Jetty getJetty() {
		return this.jetty;
	}

	public Netty getNetty() {
		return this.netty;
	}

	public Undertow getUndertow() {
		return this.undertow;
	}

	public ForwardHeadersStrategy getForwardHeadersStrategy() {
		return this.forwardHeadersStrategy;
	}

	public void setForwardHeadersStrategy(ForwardHeadersStrategy forwardHeadersStrategy) {
		this.forwardHeadersStrategy = forwardHeadersStrategy;
	}

	/**
	 * Servlet 服务器属性。
	 */
	public static class Servlet {

		/**
		 * Servlet 上下文初始化参数。
		 */
		private final Map<String, String> contextParameters = new HashMap<>();

		/**
		 * 应用的上下文路径。
		 */
		private String contextPath;

		/**
		 * 应用的显示名称。
		 */
		private String applicationDisplayName = "application";

		/**
		 * 是否向容器注册默认的 Servlet。
		 */
		private boolean registerDefaultServlet = false;

		@NestedConfigurationProperty
		private final Encoding encoding = new Encoding();

		@NestedConfigurationProperty
		private final Jsp jsp = new Jsp();

		@NestedConfigurationProperty
		private final Session session = new Session();

		public String getContextPath() {
			return this.contextPath;
		}

		public void setContextPath(String contextPath) {
			this.contextPath = cleanContextPath(contextPath);
		}

		private String cleanContextPath(String contextPath) {
			String candidate = StringUtils.trimWhitespace(contextPath);
			if (StringUtils.hasText(candidate) && candidate.endsWith("/")) {
				return candidate.substring(0, candidate.length() - 1);
			}
			return candidate;
		}

		public String getApplicationDisplayName() {
			return this.applicationDisplayName;
		}

		public void setApplicationDisplayName(String displayName) {
			this.applicationDisplayName = displayName;
		}

		public boolean isRegisterDefaultServlet() {
			return this.registerDefaultServlet;
		}

		public void setRegisterDefaultServlet(boolean registerDefaultServlet) {
			this.registerDefaultServlet = registerDefaultServlet;
		}

		public Map<String, String> getContextParameters() {
			return this.contextParameters;
		}

		public Encoding getEncoding() {
			return this.encoding;
		}

		public Jsp getJsp() {
			return this.jsp;
		}

		public Session getSession() {
			return this.session;
		}

	}

	/**
	 * Reactive 服务器属性。
	 */
	public static class Reactive {

		private final Session session = new Session();

		public Session getSession() {
			return this.session;
		}

		public static class Session {

			/**
			 * Session 超时时间。如果未指定时长后缀，则默认使用秒。
			 */
			@DurationUnit(ChronoUnit.SECONDS)
			private Duration timeout = Duration.ofMinutes(30);

			private final Cookie cookie = new Cookie();

			public Duration getTimeout() {
				return this.timeout;
			}

			public void setTimeout(Duration timeout) {
				this.timeout = timeout;
			}

			public Cookie getCookie() {
				return this.cookie;
			}

		}

	}

	/**
	 * Tomcat 属性配置。
	 */
	public static class Tomcat {

		/**
		 * 访问日志配置。
		 */
		private final Accesslog accesslog = new Accesslog();

		/**
		 * 线程相关配置。
		 */
		private final Threads threads = new Threads();

		/**
		 * Tomcat 基础目录。如果未指定，则使用临时目录。
		 */
		private File basedir;

		/**
		 * backgroundProcess 方法调用之间的延迟。如果未指定时长后缀，则默认使用秒。
		 */
		@DurationUnit(ChronoUnit.SECONDS)
		private Duration backgroundProcessorDelay = Duration.ofSeconds(10);

		/**
		 * HTTP POST 请求中表单内容的最大大小。
		 */
		private DataSize maxHttpFormPostSize = DataSize.ofMegabytes(2);

		/**
		 * 最大可吞吐的请求体大小。
		 */
		private DataSize maxSwallowSize = DataSize.ofMegabytes(2);

		/**
		 * 是否将对上下文根的请求重定向，方法是向路径末尾添加 "/"。如果 SSL 由代理终止，建议将此属性设置为 false。
		 */
		private Boolean redirectContextRoot = true;

		/**
		 * HTTP 1.1 及更高版本中，调用 sendRedirect 生成的 Location 头是使用相对重定向还是绝对重定向。
		 */
		private boolean useRelativeRedirects;

		/**
		 * 用于解码 URI 的字符编码。
		 */
		private Charset uriEncoding = StandardCharsets.UTF_8;

		/**
		 * 服务器在任何时刻接受和处理的最大连接数。达到该限制后，操作系统可能仍会基于 "acceptCount" 属性接受连接。
		 */
		private int maxConnections = 8192;

		/**
		 * 当所有请求处理线程均被占用时，等待处理的传入连接请求的最大队列长度。
		 */
		private int acceptCount = 100;

		/**
		 * 缓存中保留的最大空闲处理器数量，用于后续请求的重用。设置为 -1 时，缓存大小无限，理论最大值等于最大连接数。
		 */
		private int processorCache = 200;

		/**
		 * 在关闭连接之前等待下一个 HTTP 请求的时间。当未设置时，使用 connectionTimeout。设置为 -1 时表示无限等待。
		 */
		private Duration keepAliveTimeout;

		/**
		 * 在关闭连接之前，允许管道化的最大 HTTP 请求数。设置为 0 或 1 时，禁用保持活动连接和管道化。设置为 -1 时，允许无限数量的管道化或保持活动连接请求。
		 */
		private int maxKeepAliveRequests = 100;

		/**
		 * 逗号分隔的附加匹配 JAR 文件的模式列表，这些 JAR 文件将在 TLD 扫描时被忽略。特殊字符 '?' 匹配一个字符，'*' 匹配零个或多个字符。
		 */
		private List<String> additionalTldSkipPatterns = new ArrayList<>();

		/**
		 * 逗号分隔的附加未编码字符列表，允许在 URI 路径中出现。仅允许的字符包括 "< > [ \ ] ^ ` { | }"。
		 */
		private List<Character> relaxedPathChars = new ArrayList<>();

		/**
		 * 逗号分隔的附加未编码字符列表，允许在 URI 查询字符串中出现。仅允许的字符包括 "< > [ \ ] ^ ` { | }"。
		 */
		private List<Character> relaxedQueryChars = new ArrayList<>();

		/**
		 * 连接器接受连接后，等待请求 URI 行呈现的最长时间。
		 */
		private Duration connectionTimeout;

		/**
		 * 是否拒绝含有非法头名称或头值的请求。
		 */
		private boolean rejectIllegalHeader = true;

		/**
		 * 静态资源配置。
		 */
		private final Resource resource = new Resource();

		/**
		 * Modeler MBean 注册表配置。
		 */
		private final Mbeanregistry mbeanregistry = new Mbeanregistry();

		/**
		 * 远程 IP 阀门配置。
		 */
		private final Remoteip remoteip = new Remoteip();

		public DataSize getMaxHttpFormPostSize() {
			return this.maxHttpFormPostSize;
		}

		public void setMaxHttpFormPostSize(DataSize maxHttpFormPostSize) {
			this.maxHttpFormPostSize = maxHttpFormPostSize;
		}

		public Accesslog getAccesslog() {
			return this.accesslog;
		}

		public Threads getThreads() {
			return this.threads;
		}

		public Duration getBackgroundProcessorDelay() {
			return this.backgroundProcessorDelay;
		}

		public void setBackgroundProcessorDelay(Duration backgroundProcessorDelay) {
			this.backgroundProcessorDelay = backgroundProcessorDelay;
		}

		public File getBasedir() {
			return this.basedir;
		}

		public void setBasedir(File basedir) {
			this.basedir = basedir;
		}

		public Boolean getRedirectContextRoot() {
			return this.redirectContextRoot;
		}

		public void setRedirectContextRoot(Boolean redirectContextRoot) {
			this.redirectContextRoot = redirectContextRoot;
		}

		public boolean isUseRelativeRedirects() {
			return this.useRelativeRedirects;
		}

		public void setUseRelativeRedirects(boolean useRelativeRedirects) {
			this.useRelativeRedirects = useRelativeRedirects;
		}

		public Charset getUriEncoding() {
			return this.uriEncoding;
		}

		public void setUriEncoding(Charset uriEncoding) {
			this.uriEncoding = uriEncoding;
		}

		public int getMaxConnections() {
			return this.maxConnections;
		}

		public void setMaxConnections(int maxConnections) {
			this.maxConnections = maxConnections;
		}

		public DataSize getMaxSwallowSize() {
			return this.maxSwallowSize;
		}

		public void setMaxSwallowSize(DataSize maxSwallowSize) {
			this.maxSwallowSize = maxSwallowSize;
		}

		public int getAcceptCount() {
			return this.acceptCount;
		}

		public void setAcceptCount(int acceptCount) {
			this.acceptCount = acceptCount;
		}

		public int getProcessorCache() {
			return this.processorCache;
		}

		public void setProcessorCache(int processorCache) {
			this.processorCache = processorCache;
		}

		public Duration getKeepAliveTimeout() {
			return this.keepAliveTimeout;
		}

		public void setKeepAliveTimeout(Duration keepAliveTimeout) {
			this.keepAliveTimeout = keepAliveTimeout;
		}

		public int getMaxKeepAliveRequests() {
			return this.maxKeepAliveRequests;
		}

		public void setMaxKeepAliveRequests(int maxKeepAliveRequests) {
			this.maxKeepAliveRequests = maxKeepAliveRequests;
		}

		public List<String> getAdditionalTldSkipPatterns() {
			return this.additionalTldSkipPatterns;
		}

		public void setAdditionalTldSkipPatterns(List<String> additionalTldSkipPatterns) {
			this.additionalTldSkipPatterns = additionalTldSkipPatterns;
		}

		public List<Character> getRelaxedPathChars() {
			return this.relaxedPathChars;
		}

		public void setRelaxedPathChars(List<Character> relaxedPathChars) {
			this.relaxedPathChars = relaxedPathChars;
		}

		public List<Character> getRelaxedQueryChars() {
			return this.relaxedQueryChars;
		}

		public void setRelaxedQueryChars(List<Character> relaxedQueryChars) {
			this.relaxedQueryChars = relaxedQueryChars;
		}

		public Duration getConnectionTimeout() {
			return this.connectionTimeout;
		}

		public void setConnectionTimeout(Duration connectionTimeout) {
			this.connectionTimeout = connectionTimeout;
		}

		public boolean isRejectIllegalHeader() {
			return this.rejectIllegalHeader;
		}

		public void setRejectIllegalHeader(boolean rejectIllegalHeader) {
			this.rejectIllegalHeader = rejectIllegalHeader;
		}

		public Resource getResource() {
			return this.resource;
		}

		public Mbeanregistry getMbeanregistry() {
			return this.mbeanregistry;
		}

		public Remoteip getRemoteip() {
			return this.remoteip;
		}

		/**
		 * Tomcat 访问日志属性。
		 */
		public static class Accesslog {

			/**
			 * 是否启用访问日志。
			 */
			private boolean enabled = false;

			/**
			 * 仅当 ServletRequest.getAttribute(conditionIf) 不为 null 时，才启用请求日志记录的条件。
			 */
			private String conditionIf;

			/**
			 * 仅当 ServletRequest.getAttribute(conditionUnless) 为 null 时，才启用请求日志记录的条件。
			 */
			private String conditionUnless;

			/**
			 * 访问日志的格式模式。
			 */
			private String pattern = "common";

			/**
			 * 日志文件生成目录，可以是绝对路径，也可以是相对于 Tomcat 基目录的路径。
			 */
			private String directory = "logs";

			/**
			 * 日志文件名的前缀。
			 */
			protected String prefix = "access_log";

			/**
			 * 日志文件名的后缀。
			 */
			private String suffix = ".log";

			/**
			 * 日志文件使用的字符集。默认使用系统默认字符集。
			 */
			private String encoding;

			/**
			 * 用于格式化日志条目中的时间戳和日志文件名后缀的区域设置。默认使用Java进程的默认区域设置。
			 */
			private String locale;

			/**
			 * 是否检查日志文件是否存在，以便在外部进程重命名后重新创建日志文件。
			 */
			private boolean checkExists = false;

			/**
			 * 是否启用访问日志轮转。
			 */
			private boolean rotate = true;

			/**
			 * 是否延迟在文件名中包含日期标记，直到轮转时刻。
			 */
			private boolean renameOnRotate = false;

			/**
			 * 保留访问日志文件的天数，超过该天数的文件将被删除。
			 */
			private int maxDays = -1;

			/**
			 * 日志文件名中日期的格式。
			 */
			private String fileDateFormat = ".yyyy-MM-dd";

			/**
			 * 是否使用 RFC 5952 定义的 IPv6 规范格式。
			 */
			private boolean ipv6Canonical = false;

			/**
			 * 是否为请求设置IP地址、主机名、协议和端口等属性。
			 */
			private boolean requestAttributesEnabled = false;

			/**
			 * 是否对输出进行缓冲，使其仅周期性地刷新。
			 */
			private boolean buffered = true;

			public boolean isEnabled() {
				return this.enabled;
			}

			public void setEnabled(boolean enabled) {
				this.enabled = enabled;
			}

			public String getConditionIf() {
				return this.conditionIf;
			}

			public void setConditionIf(String conditionIf) {
				this.conditionIf = conditionIf;
			}

			public String getConditionUnless() {
				return this.conditionUnless;
			}

			public void setConditionUnless(String conditionUnless) {
				this.conditionUnless = conditionUnless;
			}

			public String getPattern() {
				return this.pattern;
			}

			public void setPattern(String pattern) {
				this.pattern = pattern;
			}

			public String getDirectory() {
				return this.directory;
			}

			public void setDirectory(String directory) {
				this.directory = directory;
			}

			public String getPrefix() {
				return this.prefix;
			}

			public void setPrefix(String prefix) {
				this.prefix = prefix;
			}

			public String getSuffix() {
				return this.suffix;
			}

			public void setSuffix(String suffix) {
				this.suffix = suffix;
			}

			public String getEncoding() {
				return this.encoding;
			}

			public void setEncoding(String encoding) {
				this.encoding = encoding;
			}

			public String getLocale() {
				return this.locale;
			}

			public void setLocale(String locale) {
				this.locale = locale;
			}

			public boolean isCheckExists() {
				return this.checkExists;
			}

			public void setCheckExists(boolean checkExists) {
				this.checkExists = checkExists;
			}

			public boolean isRotate() {
				return this.rotate;
			}

			public void setRotate(boolean rotate) {
				this.rotate = rotate;
			}

			public boolean isRenameOnRotate() {
				return this.renameOnRotate;
			}

			public void setRenameOnRotate(boolean renameOnRotate) {
				this.renameOnRotate = renameOnRotate;
			}

			public int getMaxDays() {
				return this.maxDays;
			}

			public void setMaxDays(int maxDays) {
				this.maxDays = maxDays;
			}

			public String getFileDateFormat() {
				return this.fileDateFormat;
			}

			public void setFileDateFormat(String fileDateFormat) {
				this.fileDateFormat = fileDateFormat;
			}

			public boolean isIpv6Canonical() {
				return this.ipv6Canonical;
			}

			public void setIpv6Canonical(boolean ipv6Canonical) {
				this.ipv6Canonical = ipv6Canonical;
			}

			public boolean isRequestAttributesEnabled() {
				return this.requestAttributesEnabled;
			}

			public void setRequestAttributesEnabled(boolean requestAttributesEnabled) {
				this.requestAttributesEnabled = requestAttributesEnabled;
			}

			public boolean isBuffered() {
				return this.buffered;
			}

			public void setBuffered(boolean buffered) {
				this.buffered = buffered;
			}

		}

		/**
		 * Tomcat 线程属性配置类。
		 */
		public static class Threads {

			/**
			 * 最大工作线程数。
			 */
			private int max = 200;

			/**
			 * 最小空闲工作线程数。
			 */
			private int minSpare = 10;

			public int getMax() {
				return this.max;
			}

			public void setMax(int max) {
				this.max = max;
			}

			public int getMinSpare() {
				return this.minSpare;
			}

			public void setMinSpare(int minSpare) {
				this.minSpare = minSpare;
			}

		}

		/**
		 * Tomcat 静态资源属性配置类。
		 */
		public static class Resource {

			/**
			 * 是否允许该 Web 应用的静态资源缓存。
			 */
			private boolean allowCaching = true;

			/**
			 * 静态资源缓存的存活时间（TTL）。
			 */
			private Duration cacheTtl;

			public boolean isAllowCaching() {
				return this.allowCaching;
			}

			public void setAllowCaching(boolean allowCaching) {
				this.allowCaching = allowCaching;
			}

			public Duration getCacheTtl() {
				return this.cacheTtl;
			}

			public void setCacheTtl(Duration cacheTtl) {
				this.cacheTtl = cacheTtl;
			}

		}

		/**
		 * Tomcat MBean 注册表配置类。
		 */
		public static class Mbeanregistry {

			/**
			 * 是否启用 Tomcat 的 MBean 注册表。
			 */
			private boolean enabled;

			public boolean isEnabled() {
				return this.enabled;
			}

			public void setEnabled(boolean enabled) {
				this.enabled = enabled;
			}

		}

		public static class Remoteip {

			/**
			 * 表示受信任代理服务器的正则表达式，用于匹配内部代理地址。
			 */
			private String internalProxies = "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 10/8
					+ "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" // 192.168/16
					+ "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" // 169.254/16
					+ "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 127/8
					+ "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" // 172.16/12
					+ "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}|" //
					+ "0:0:0:0:0:0:0:1|::1";

			/**
			 * 存储请求协议的 HTTP 头名称，通常是 "X-Forwarded-Proto"。
			 */
			private String protocolHeader;

			/**
			 * 协议头中表示使用 SSL 的值，默认是 "https"。
			 */
			private String protocolHeaderHttpsValue = "https";

			/**
			 * 存储远程主机信息的 HTTP 头名称，默认是 "X-Forwarded-Host"。
			 */
			private String hostHeader = "X-Forwarded-Host";

			/**
			 * 用于覆盖原始端口值的 HTTP 头名称，默认是 "X-Forwarded-Port"。
			 */
			private String portHeader = "X-Forwarded-Port";

			/**
			 * 存储远程 IP 的 HTTP 头名称，例如 "X-FORWARDED-FOR"。
			 */
			private String remoteIpHeader;

			public String getInternalProxies() {
				return this.internalProxies;
			}

			public void setInternalProxies(String internalProxies) {
				this.internalProxies = internalProxies;
			}

			public String getProtocolHeader() {
				return this.protocolHeader;
			}

			public void setProtocolHeader(String protocolHeader) {
				this.protocolHeader = protocolHeader;
			}

			public String getProtocolHeaderHttpsValue() {
				return this.protocolHeaderHttpsValue;
			}

			public String getHostHeader() {
				return this.hostHeader;
			}

			public void setHostHeader(String hostHeader) {
				this.hostHeader = hostHeader;
			}

			public void setProtocolHeaderHttpsValue(String protocolHeaderHttpsValue) {
				this.protocolHeaderHttpsValue = protocolHeaderHttpsValue;
			}

			public String getPortHeader() {
				return this.portHeader;
			}

			public void setPortHeader(String portHeader) {
				this.portHeader = portHeader;
			}

			public String getRemoteIpHeader() {
				return this.remoteIpHeader;
			}

			public void setRemoteIpHeader(String remoteIpHeader) {
				this.remoteIpHeader = remoteIpHeader;
			}

		}

	}

	/**
	 * Jetty 服务器相关配置属性。
	 */
	public static class Jetty {

		/**
		 * 访问日志配置。
		 */
		private final Accesslog accesslog = new Accesslog();

		/**
		 * 线程相关配置。
		 */
		private final Threads threads = new Threads();

		/**
		 * 任何 HTTP POST 请求中表单内容的最大大小。
		 */
		private DataSize maxHttpFormPostSize = DataSize.ofBytes(200000);

		/**
		 * 连接允许空闲的最长时间，超过该时间后连接将被关闭。
		 */
		private Duration connectionIdleTimeout;

		public Accesslog getAccesslog() {
			return this.accesslog;
		}

		public Threads getThreads() {
			return this.threads;
		}

		public DataSize getMaxHttpFormPostSize() {
			return this.maxHttpFormPostSize;
		}

		public void setMaxHttpFormPostSize(DataSize maxHttpFormPostSize) {
			this.maxHttpFormPostSize = maxHttpFormPostSize;
		}

		public Duration getConnectionIdleTimeout() {
			return this.connectionIdleTimeout;
		}

		public void setConnectionIdleTimeout(Duration connectionIdleTimeout) {
			this.connectionIdleTimeout = connectionIdleTimeout;
		}

		/**
		 * Jetty 访问日志属性配置。
		 */
		public static class Accesslog {

			/**
			 * 是否启用访问日志。
			 */
			private boolean enabled = false;

			/**
			 * 日志格式。
			 */
			private FORMAT format = FORMAT.NCSA;

			/**
			 * 自定义日志格式，参见 org.eclipse.jetty.server.CustomRequestLog。
			 * 如果定义了此项，则会覆盖 "format" 配置项。
			 */
			private String customFormat;

			/**
			 * 日志文件名。如果未指定，则日志会重定向到 "System.err"。
			 */
			private String filename;

			/**
			 * 日志文件名中的日期格式。
			 */
			private String fileDateFormat;

			/**
			 * 旋转日志文件删除之前保留的天数。
			 */
			private int retentionPeriod = 31; // 保留天数

			/**
			 * 是否追加日志内容。
			 */
			private boolean append;

			/**
			 * 不应该被记录日志的请求路径列表。
			 */
			private List<String> ignorePaths;

			public boolean isEnabled() {
				return this.enabled;
			}

			public void setEnabled(boolean enabled) {
				this.enabled = enabled;
			}

			public FORMAT getFormat() {
				return this.format;
			}

			public void setFormat(FORMAT format) {
				this.format = format;
			}

			public String getCustomFormat() {
				return this.customFormat;
			}

			public void setCustomFormat(String customFormat) {
				this.customFormat = customFormat;
			}

			public String getFilename() {
				return this.filename;
			}

			public void setFilename(String filename) {
				this.filename = filename;
			}

			public String getFileDateFormat() {
				return this.fileDateFormat;
			}

			public void setFileDateFormat(String fileDateFormat) {
				this.fileDateFormat = fileDateFormat;
			}

			public int getRetentionPeriod() {
				return this.retentionPeriod;
			}

			public void setRetentionPeriod(int retentionPeriod) {
				this.retentionPeriod = retentionPeriod;
			}

			public boolean isAppend() {
				return this.append;
			}

			public void setAppend(boolean append) {
				this.append = append;
			}

			public List<String> getIgnorePaths() {
				return this.ignorePaths;
			}

			public void setIgnorePaths(List<String> ignorePaths) {
				this.ignorePaths = ignorePaths;
			}

			/**
			 * Jetty 访问日志的日志格式枚举。
			 */
			public enum FORMAT {

				/**
				 * NCSA 格式，定义见 CustomRequestLog#NCSA_FORMAT。
				 */
				NCSA,

				/**
				 * 扩展的 NCSA 格式，定义见 CustomRequestLog#EXTENDED_NCSA_FORMAT。
				 */
				EXTENDED_NCSA

			}

		}

		/**
		 * Jetty 线程相关属性。
		 */
		public static class Threads {

			/**
			 * 使用的接收线程（acceptor）数量。值为 -1 时表示默认配置，即根据操作系统环境自动推导。
			 */
			private Integer acceptors = -1;

			/**
			 * 使用的选择器线程（selector）数量。值为 -1 时表示默认配置，即根据操作系统环境自动推导。
			 */
			private Integer selectors = -1;

			/**
			 * 线程池允许的最大线程数。
			 */
			private Integer max = 200;

			/**
			 * 线程池保留的最小线程数。
			 */
			private Integer min = 8;

			/**
			 * 线程池后台队列的最大容量。如果未设置，将根据线程配置自动计算默认值。
			 */
			private Integer maxQueueCapacity;

			/**
			 * 最大线程空闲时间。
			 */
			private Duration idleTimeout = Duration.ofMillis(60000);

			public Integer getAcceptors() {
				return this.acceptors;
			}

			public void setAcceptors(Integer acceptors) {
				this.acceptors = acceptors;
			}

			public Integer getSelectors() {
				return this.selectors;
			}

			public void setSelectors(Integer selectors) {
				this.selectors = selectors;
			}

			public void setMin(Integer min) {
				this.min = min;
			}

			public Integer getMin() {
				return this.min;
			}

			public void setMax(Integer max) {
				this.max = max;
			}

			public Integer getMax() {
				return this.max;
			}

			public Integer getMaxQueueCapacity() {
				return this.maxQueueCapacity;
			}

			public void setMaxQueueCapacity(Integer maxQueueCapacity) {
				this.maxQueueCapacity = maxQueueCapacity;
			}

			public void setIdleTimeout(Duration idleTimeout) {
				this.idleTimeout = idleTimeout;
			}

			public Duration getIdleTimeout() {
				return this.idleTimeout;
			}

		}

	}

	/**
	 * Netty 配置属性。
	 */
	public static class Netty {

		/**
		 * Netty 通道的连接超时时间。
		 */
		private Duration connectionTimeout;

		/**
		 * H2C 升级请求的最大内容长度。
		 */
		private DataSize h2cMaxContentLength = DataSize.ofBytes(0);

		/**
		 * HTTP 请求解码的初始缓冲区大小。
		 */
		private DataSize initialBufferSize = DataSize.ofBytes(128);

		/**
		 * HTTP 请求可解码的最大分块大小。
		 */
		private DataSize maxChunkSize = DataSize.ofKilobytes(8);

		/**
		 * HTTP 请求初始行可解码的最大长度。
		 */
		private DataSize maxInitialLineLength = DataSize.ofKilobytes(4);

		/**
		 * 每个连接允许的最大请求数。默认情况下，连接可以处理无限数量的请求。
		 */
		private Integer maxKeepAliveRequests;

		/**
		 * 解码请求时是否校验请求头。
		 */
		private boolean validateHeaders = true;

		/**
		 * Netty 通道的空闲超时时间。如果未指定，则表示无限超时。
		 */
		private Duration idleTimeout;

		public Duration getConnectionTimeout() {
			return this.connectionTimeout;
		}

		public void setConnectionTimeout(Duration connectionTimeout) {
			this.connectionTimeout = connectionTimeout;
		}

		public DataSize getH2cMaxContentLength() {
			return this.h2cMaxContentLength;
		}

		public void setH2cMaxContentLength(DataSize h2cMaxContentLength) {
			this.h2cMaxContentLength = h2cMaxContentLength;
		}

		public DataSize getInitialBufferSize() {
			return this.initialBufferSize;
		}

		public void setInitialBufferSize(DataSize initialBufferSize) {
			this.initialBufferSize = initialBufferSize;
		}

		public DataSize getMaxChunkSize() {
			return this.maxChunkSize;
		}

		public void setMaxChunkSize(DataSize maxChunkSize) {
			this.maxChunkSize = maxChunkSize;
		}

		public DataSize getMaxInitialLineLength() {
			return this.maxInitialLineLength;
		}

		public void setMaxInitialLineLength(DataSize maxInitialLineLength) {
			this.maxInitialLineLength = maxInitialLineLength;
		}

		public Integer getMaxKeepAliveRequests() {
			return this.maxKeepAliveRequests;
		}

		public void setMaxKeepAliveRequests(Integer maxKeepAliveRequests) {
			this.maxKeepAliveRequests = maxKeepAliveRequests;
		}

		public boolean isValidateHeaders() {
			return this.validateHeaders;
		}

		public void setValidateHeaders(boolean validateHeaders) {
			this.validateHeaders = validateHeaders;
		}

		public Duration getIdleTimeout() {
			return this.idleTimeout;
		}

		public void setIdleTimeout(Duration idleTimeout) {
			this.idleTimeout = idleTimeout;
		}

	}

	/**
	 * Undertow 配置属性。
	 */
	public static class Undertow {

		/**
		 * HTTP POST 内容的最大大小。若值为 -1（默认值），则表示不限制大小。
		 */
		private DataSize maxHttpPostSize = DataSize.ofBytes(-1);

		/**
		 * 每个缓冲区的大小。默认值依据 JVM 可用的最大内存自动推导。
		 */
		private DataSize bufferSize;

		/**
		 * 是否在 Java 堆外分配缓冲区。默认值依据 JVM 可用的最大内存自动推导。
		 */
		private Boolean directBuffers;

		/**
		 * 是否在启动时初始化 Servlet 过滤器。
		 */
		private boolean eagerFilterInit = true;

		/**
		 * 允许的最大查询参数或路径参数数量。该限制用于防止基于哈希冲突的 DOS 攻击。
		 */
		private int maxParameters = UndertowOptions.DEFAULT_MAX_PARAMETERS;

		/**
		 * 允许的最大请求头数量。该限制用于防止基于哈希冲突的 DOS 攻击。
		 */
		private int maxHeaders = UndertowOptions.DEFAULT_MAX_HEADERS;

		/**
		 * 允许的最大 Cookie 数量。此限制用于防止基于哈希冲突的拒绝服务（DOS）攻击。
		 */
		private int maxCookies = 200;

		/**
		 * 服务器是否应解码百分号编码的斜杠字符。启用编码斜杠可能存在安全风险，
		 * 因为不同的服务器对斜杠的解释可能不同。仅在旧应用需要时才启用此项。
		 */
		private boolean allowEncodedSlash = false;

		/**
		 * 是否对 URL 进行解码。如果禁用，URL 中的百分号编码字符将保留原样。
		 */
		private boolean decodeUrl = true;

		/**
		 * 用于解码 URL 的字符集。
		 */
		private Charset urlCharset = StandardCharsets.UTF_8;

		/**
		 * 是否在所有响应中添加 "Connection: keep-alive" 头部，
		 * 即使 HTTP 规范未强制要求。
		 */
		private boolean alwaysSetKeepAlive = true;

		/**
		 * 在没有处理请求的情况下，连接可以保持空闲的最长时间，
		 * 超过该时间服务器将关闭连接。
		 */
		private Duration noRequestTimeout;

		/**
		 * 在请求转发时是否保留原始路径。
		 */
		private boolean preservePathOnForward = false;

		private final Accesslog accesslog = new Accesslog();

		/**
		 * 线程相关配置。
		 */
		private final Threads threads = new Threads();

		private final Options options = new Options();

		public DataSize getMaxHttpPostSize() {
			return this.maxHttpPostSize;
		}

		public void setMaxHttpPostSize(DataSize maxHttpPostSize) {
			this.maxHttpPostSize = maxHttpPostSize;
		}

		public DataSize getBufferSize() {
			return this.bufferSize;
		}

		public void setBufferSize(DataSize bufferSize) {
			this.bufferSize = bufferSize;
		}

		public Boolean getDirectBuffers() {
			return this.directBuffers;
		}

		public void setDirectBuffers(Boolean directBuffers) {
			this.directBuffers = directBuffers;
		}

		public boolean isEagerFilterInit() {
			return this.eagerFilterInit;
		}

		public void setEagerFilterInit(boolean eagerFilterInit) {
			this.eagerFilterInit = eagerFilterInit;
		}

		public int getMaxParameters() {
			return this.maxParameters;
		}

		public void setMaxParameters(Integer maxParameters) {
			this.maxParameters = maxParameters;
		}

		public int getMaxHeaders() {
			return this.maxHeaders;
		}

		public void setMaxHeaders(int maxHeaders) {
			this.maxHeaders = maxHeaders;
		}

		public Integer getMaxCookies() {
			return this.maxCookies;
		}

		public void setMaxCookies(Integer maxCookies) {
			this.maxCookies = maxCookies;
		}

		public boolean isAllowEncodedSlash() {
			return this.allowEncodedSlash;
		}

		public void setAllowEncodedSlash(boolean allowEncodedSlash) {
			this.allowEncodedSlash = allowEncodedSlash;
		}

		public boolean isDecodeUrl() {
			return this.decodeUrl;
		}

		public void setDecodeUrl(Boolean decodeUrl) {
			this.decodeUrl = decodeUrl;
		}

		public Charset getUrlCharset() {
			return this.urlCharset;
		}

		public void setUrlCharset(Charset urlCharset) {
			this.urlCharset = urlCharset;
		}

		public boolean isAlwaysSetKeepAlive() {
			return this.alwaysSetKeepAlive;
		}

		public void setAlwaysSetKeepAlive(boolean alwaysSetKeepAlive) {
			this.alwaysSetKeepAlive = alwaysSetKeepAlive;
		}

		public Duration getNoRequestTimeout() {
			return this.noRequestTimeout;
		}

		public void setNoRequestTimeout(Duration noRequestTimeout) {
			this.noRequestTimeout = noRequestTimeout;
		}

		public boolean isPreservePathOnForward() {
			return this.preservePathOnForward;
		}

		public void setPreservePathOnForward(boolean preservePathOnForward) {
			this.preservePathOnForward = preservePathOnForward;
		}

		public Accesslog getAccesslog() {
			return this.accesslog;
		}

		public Threads getThreads() {
			return this.threads;
		}

		public Options getOptions() {
			return this.options;
		}

		/**
		 * Undertow 访问日志相关配置。
		 */
		public static class Accesslog {

			/**
			 * 是否启用访问日志。
			 */
			private boolean enabled = false;

			/**
			 * 访问日志的格式模式。
			 */
			private String pattern = "common";

			/**
			 * 日志文件名前缀。
			 */
			protected String prefix = "access_log.";

			/**
			 * 日志文件名后缀。
			 */
			private String suffix = "log";

			/**
			 * Undertow 访问日志的存储目录。
			 */
			private File dir = new File("logs");

			/**
			 * 是否启用访问日志轮转（日志滚动）。
			 */
			private boolean rotate = true;

			public boolean isEnabled() {
				return this.enabled;
			}

			public void setEnabled(boolean enabled) {
				this.enabled = enabled;
			}

			public String getPattern() {
				return this.pattern;
			}

			public void setPattern(String pattern) {
				this.pattern = pattern;
			}

			public String getPrefix() {
				return this.prefix;
			}

			public void setPrefix(String prefix) {
				this.prefix = prefix;
			}

			public String getSuffix() {
				return this.suffix;
			}

			public void setSuffix(String suffix) {
				this.suffix = suffix;
			}

			public File getDir() {
				return this.dir;
			}

			public void setDir(File dir) {
				this.dir = dir;
			}

			public boolean isRotate() {
				return this.rotate;
			}

			public void setRotate(boolean rotate) {
				this.rotate = rotate;
			}

		}

		/**
		 * Undertow 线程相关配置。
		 */
		public static class Threads {

			/**
			 * 为 worker 创建的 I/O 线程数量。默认值根据可用处理器数量推导得出。
			 */
			private Integer io;

			/**
			 * Worker 线程数量。默认值为 I/O 线程数量的 8 倍。
			 */
			private Integer worker;

			public Integer getIo() {
				return this.io;
			}

			public void setIo(Integer io) {
				this.io = io;
			}

			public Integer getWorker() {
				return this.worker;
			}

			public void setWorker(Integer worker) {
				this.worker = worker;
			}

		}

		public static class Options {

			private Map<String, String> socket = new LinkedHashMap<>();

			private Map<String, String> server = new LinkedHashMap<>();

			public Map<String, String> getServer() {
				return this.server;
			}

			public Map<String, String> getSocket() {
				return this.socket;
			}

		}

	}

	/**
	 * 支持 Forward 头信息的策略枚举。
	 */
	public enum ForwardHeadersStrategy {

		/**
		 * 使用底层容器对 Forward 头信息的原生支持。
		 */
		NATIVE,

		/**
		 * 使用 Spring 提供的 Forward 头信息处理支持。
		 */
		FRAMEWORK,

		/**
		 * 忽略所有 X-Forwarded-* 头信息。
		 */
		NONE

	}

}
